﻿// IPC Library
// Interprocess Communication Library
// Copyright(C)2008 Bill Tudor

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;

namespace IPCLibrary
{
	/// <summary>
	/// Access to shared dynamic library data.
	/// </summary>
	/// <remarks>
	/// The SharedMemory class provides access to shared "data" located in a dynamic
	/// link library.
	/// </remarks>
	public class SharedMemory :IDisposable
	{
		private string _moduleName;
		private string _name;
		private int _size;
		private bool _attached;
		private LoadLibrarySafeHandle _module;
		private IntPtr _address;
		private bool _disposed;
		private Mutex _mutex;
		/// <summary>
		/// Shared data module.
		/// </summary>
		public string ModuleName
		{
			get { return _moduleName; }
		}
		/// <summary>
		/// Shared data name.
		/// </summary>
		public string Name
		{
			get { return _name; }
		}
		/// <summary>
		/// Shared data size.
		/// </summary>
		public int Size
		{
			get { return _size; }
		}
		/// <summary>
		/// Finalizer.
		/// </summary>
		~SharedMemory() { Dispose(false); }
		/// <summary>
		/// Create shared memory.
		/// </summary>
		/// <param name="moduleName">Shared data module.</param>
		/// <param name="name">Shared data name.</param>
		/// <param name="size">Shared data size.</param>
		public SharedMemory(string moduleName, string name, int size)
		{
			_moduleName = moduleName;
			_name = name;
			_size = size;
			_address = IntPtr.Zero;
		}
		/// <summary>
		/// Attach to shared data.
		/// </summary>
		/// <returns>True if successfully attached.</returns>
		public bool Attach()
		{
			EnsureAttached();
			return _attached;
		}
		/// <summary>
		/// Detach/Release shared data.
		/// </summary>
		/// <returns>True if successfully detached.</returns>
		public bool Detach()
		{
			if (_attached)
			{
				lock (this)
				{
					if (_attached)
					{
						// Unload Library
						_module.Dispose();
						_address = IntPtr.Zero;
						_attached = false;
					}
				}
			}
			return !_attached;
		}
		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="dataOffset">Offset into shared memory.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Bytes read.</returns>
		public byte[] ReadBytes(int dataOffset, int count)
		{
			byte[] result = new byte[count];
			ReadBytes(result, 0, dataOffset, count);
			return result;
		}
		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="buffer">Buffer to receive the bytes.</param>
		/// <param name="dataOffset">Offset into shared memory to start reading.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Number of bytes read.</returns>
		public int ReadBytes(byte[] buffer, int dataOffset, int count)
		{
			return ReadBytes(buffer, 0, dataOffset, count);
		}
		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="buffer">Buffer to receive the bytes.</param>
		/// <param name="bufferOffset">Offset in the receive buffer to copy to.</param>
		/// <param name="dataOffset">Offset into shared memory to start reading.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Number of bytes read.</returns>
		public int ReadBytes(byte[] buffer, int bufferOffset, int dataOffset, int count)
		{
			EnsureAttached();
			Marshal.Copy(new IntPtr(_address.ToInt32()+dataOffset), buffer, bufferOffset, count);
			return count;
		}
		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="buffer">Buffer to receive the bytes.</param>
		/// <param name="bufferOffset">Offset in the receive buffer to copy to.</param>
		/// <param name="dataOffset">Offset into shared memory to start reading.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Number of bytes read.</returns>
		/// <remarks>
		/// This read operation is synchronized around a named mutex.
		/// </remarks>
		public int SynchronizedRead(byte[] buffer, int bufferOffset, int dataOffset, int count)
		{
			GrabMutex();
			int result = ReadBytes(buffer, bufferOffset, dataOffset, count);
			ReleaseMutex();
			return result;
		}
		/// <summary>
		/// Write bytes to shared memory.
		/// </summary>
		/// <param name="buffer">Source buffer.</param>
		/// <param name="dataOffset">Offset into shared memory to copy to.</param>
		/// <returns>number of bytes copied.</returns>
		public int WriteBytes(byte[] buffer, int dataOffset)
		{
			return WriteBytes(buffer, 0, dataOffset, buffer.Length);
		}
		/// <summary>
		/// Write bytes to shared memory.
		/// </summary>
		/// <param name="buffer">Source buffer.</param>
		/// <param name="dataOffset">Offset into shared memory to copy to.</param>
		/// <param name="count">Number of bytes to copy.</param>
		/// <returns>number of bytes copied.</returns>
		public int WriteBytes(byte[] buffer, int dataOffset, int count)
		{
			return WriteBytes(buffer, 0, dataOffset, count);
		}
		/// <summary>
		/// Write bytes to shared memory.
		/// </summary>
		/// <param name="buffer">Source buffer.</param>
		/// <param name="bufferOffset">Offset in the source buffer to begin the copy.</param>
		/// <param name="dataOffset">Offset into shared memory to copy to.</param>
		/// <param name="count">Number of bytes to copy.</param>
		/// <returns>number of bytes copied.</returns>
		public int WriteBytes(byte[] buffer, int bufferOffset, int dataOffset, int count)
		{
			EnsureAttached();
			Marshal.Copy(buffer, bufferOffset, new IntPtr(_address.ToInt32() + dataOffset), count);
			return count;
		}
		/// <summary>
		/// Write bytes to shared memory.
		/// </summary>
		/// <param name="buffer">Source buffer.</param>
		/// <param name="bufferOffset">Offset in the source buffer to begin the copy.</param>
		/// <param name="dataOffset">Offset into shared memory to copy to.</param>
		/// <param name="count">Number of bytes to copy.</param>
		/// <returns>number of bytes copied.</returns>
		/// <remarks>
		/// The write operation is synchronized around a named mutex.
		/// </remarks>
		public int SynchronizedWrite(byte[] buffer, int bufferOffset, int dataOffset, int count)
		{
			GrabMutex();
			int result = WriteBytes(buffer, bufferOffset, dataOffset, count);
			ReleaseMutex();
			return result;
		}

		/// <summary>
		/// Return a stream that can be used to read and write data.
		/// </summary>
		/// <returns>Stream.</returns>
		public Stream GetStream()
		{
			return new SharedMemoryStream(this);
		}
		private void EnsureAttached()
		{
			if (!_attached)
			{
				lock (this)
				{
					if (!_attached)
					{
						_module = NativeMethods.LoadLibrary(_moduleName);
						if (_module == null || _module.IsInvalid)
						{
							throw IPCException.FromLastError("Unable to load shared memory module.", "LoadLibrary");
						}
						_address = NativeMethods.GetProcAddress(_module, _name);
						if (_address == IntPtr.Zero)
						{
							_module.Dispose();
							throw IPCException.FromLastError("Unable to locate shared data.", "GetProcAddress");
						}
						_attached = true;
					}
				}
			}
		}
		private void GrabMutex()
		{
			CreateMutex();
			_mutex.WaitOne();
		}
		private void CreateMutex()
		{
			if (_mutex == null)
			{
				lock (this)
				{
					if (_mutex == null)
					{
						_mutex = new Mutex(false, _name);
					}
				}
			}
		}
		private void ReleaseMutex()
		{
			_mutex.ReleaseMutex();
		}


		#region IDisposable Members
		/// <summary>
		/// Dispose of unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		/// <summary>
		/// Dispose of unmanaged resources.
		/// </summary>
		/// <param name="disposing">True if disposing.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				Detach();
				if (_mutex != null)
				{
					_mutex.Close();
				}
			}
			_disposed = true;
		}

		#endregion
	}
}
